package main

import (
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"text/tabwriter"

	"gitee.com/wmdng/sjcc"
)

/*
src, dst
*/

type sjargs struct {
	comp bool     /* compiler only */
	verb bool     /* verbose mode */
	src  []string /* comp, link : sources files name or object files name */
	dst  string   /* comp, link : target file name */
	cpu  string   /* comp, link : arch cpu name */
	sct  string   /* link : script file name */

	macs []string /* macro define */
	incs []string /* include dirs */

	/**/
	ccargs []string
	ldargs []string
}

func myParseArgs(args []string) (*sjargs, error) {
	var i int
	var v string
	var arg sjargs

	/**/
	arg.comp = false
	arg.verb = false
	arg.cpu = "none"

	/**/
	for i = 1; i < len(os.Args); i++ {

		/**/
		v = os.Args[i]

		if !strings.HasPrefix(v, "-") {
			arg.src = append(arg.src, v)
			continue
		}

		switch {
		case v == "-h" || v == "-help" || v == "--help":
			w := tabwriter.NewWriter(os.Stdout, 0, 0, 8, ' ', tabwriter.TabIndent)
			fmt.Fprintln(w, "usage: ", os.Args[0], "[options] file...")
			fmt.Fprintln(w, "  --help\tdisplay this infomation")
			fmt.Fprintln(w, "  -cpu=xxx\tspecial the target cpu")
			fmt.Fprintln(w, "  -c\tcompile only")
			fmt.Fprintln(w, "  -o <file>\toutput file")
			fmt.Fprintln(w, "  -v\tverbose mode")
			fmt.Fprintln(w, "  -T <file>\tlink script file")
			fmt.Fprintln(w, "  -D xxx\tdefine macro")
			fmt.Fprintln(w, "  -I xxx\tinclude dir")
			w.Flush()
			return nil, errors.New("help end")

		case v == "-c":
			arg.comp = true

		case v == "-v":
			arg.verb = true

		case v == "-o":
			i += 1
			if i >= len(os.Args) {
				return nil, errors.New("-o out file name")
			}
			arg.dst = os.Args[i]

		case v == "-T":
			i += 1
			if i >= len(os.Args) {
				return nil, errors.New("-T script file name")
			}
			arg.sct = os.Args[i]

		case v == "-Wc":
			i += 1
			if i >= len(os.Args) {
				return nil, errors.New("-Wc comp args")
			}
			arg.ccargs = append(arg.ccargs, os.Args[i])

		case v == "-Wl":
			i += 1
			if i >= len(os.Args) {
				return nil, errors.New("-Wl link args")
			}
			arg.ldargs = append(arg.ldargs, os.Args[i])

		case strings.HasPrefix(v, "-cpu="):
			arg.cpu = strings.TrimPrefix(v, "-cpu=")

		case strings.HasPrefix(v, "-D"):
			if v == "-D" {

				i += 1
				if i >= len(os.Args) {
					return nil, errors.New("-D macro")
				}
				arg.macs = append(arg.macs, os.Args[i])

			} else {
				arg.macs = append(arg.macs, strings.TrimPrefix(v, "-D"))
			}

		case strings.HasPrefix(v, "-I"):
			if v == "-I" {

				i += 1
				if i >= len(os.Args) {
					return nil, errors.New("-I dir")
				}
				arg.macs = append(arg.incs, os.Args[i])

			} else {
				arg.incs = append(arg.incs, strings.TrimPrefix(v, "-I"))
			}

		default:
			return nil, errors.New("not support arg, " + v)
		}
	}

	return &arg, nil
}

/*
-c       : compile only.
-cpu=    : special cpu type
-o xxx   : output file name
*/

// func myParseArgs(args []string) (*sjargs, error) {
// 	var i int
// 	var v string
// 	var arg sjargs

// 	/**/
// 	arg.cmp = false
// 	arg.dbg = false
// 	arg.cpu = "none"

// 	/**/
// 	for i = 1; i < len(os.Args); i++ {

// 		/**/
// 		v = os.Args[i]

// 		if strings.HasPrefix(v, "-") {

// 			if v == "-c" {

// 				arg.cmp = true

// 			} else if v == "-v" {

// 				arg.dbg = true

// 			} else if v == "-o" {

// 				/**/
// 				i++
// 				if i >= len(os.Args) {
// 					return nil, errors.New("need output file name")
// 				}

// 				arg.dst = os.Args[i]

// 			} else if strings.HasPrefix(v, "-cpu=") {

// 				arg.cpu = strings.TrimPrefix(v, "-cpu=")
// 				if len(arg.cpu) <= 0 {
// 					return nil, errors.New("-cpu=??, fmt err")
// 				}

// 			} else if strings.HasPrefix(v, "-I") {

// 				arg.args = append(arg.args, v)

// 			} else if strings.HasPrefix(v, "-D") {

// 				arg.args = append(arg.args, v)

// 			} else if strings.HasPrefix(v, "-T") {

// 				if v == "-T" {

// 					i++
// 					if i >= len(os.Args) {
// 						return nil, errors.New("need script file name")
// 					}

// 					arg.sct = os.Args[i]

// 				} else {
// 					arg.sct = strings.TrimPrefix(v, "-T")
// 				}

// 			} else {
// 				return nil, errors.New("args not support:" + v)
// 			}

// 		} else {
// 			/* src */
// 			arg.src = append(arg.src, v)
// 		}
// 	}

// 	/**/
// 	return &arg, nil
// }

func myCompile(scpu string, ssrc string, sdst string, arg *sjargs) error {

	/**/
	cmd, err := sjcc.NewComp(scpu, ssrc, sdst)
	if err != nil {
		return err
	}

	/**/
	cmd.AddMacs(arg.macs)
	cmd.AddIncs(arg.incs)
	cmd.AddArgs(arg.ccargs)

	/**/
	exx, err := cmd.CreateExec()
	if err != nil {
		return err
	}

	/**/
	if arg.verb {
		fmt.Println(">> ", strings.Join(exx.Args, " "))
	}
	rult, err := exx.CombinedOutput()
	if len(rult) > 0 {
		fmt.Printf("%s\n", rult)
	}

	/**/
	return err
}

func myAssemble(scpu string, ssrc string, sdst string, arg *sjargs) error {

	/**/
	smid := ssrc + ".ps"

	/**/
	cmd, err := sjcc.NewAsmb(scpu, ssrc, smid, sdst)
	if err != nil {
		return err
	}

	/**/
	cmd.AddMacs(arg.macs)
	cmd.AddIncs(arg.incs)
	cmd.AddArgs(arg.ccargs)

	/**/
	exx, err := cmd.CreatePreproc()
	if err != nil {
		return err
	}

	/**/
	if arg.verb {
		fmt.Println(">> ", strings.Join(exx.Args, " "))
	}

	prult, err := exx.CombinedOutput()
	if len(prult) > 0 {
		fmt.Printf("%s\n", prult)
	}

	if err != nil {
		return err
	}

	/**/
	eas, err := cmd.CreateAssemble()
	if err != nil {
		os.Remove(smid)
		return err
	}

	/**/
	if arg.verb {
		fmt.Println(">> ", strings.Join(eas.Args, " "))
	}

	arult, err := eas.CombinedOutput()
	if len(arult) > 0 {
		fmt.Printf("%s\n", arult)
	}

	/**/
	os.Remove(smid)
	return err
}

func myLink(scpu string, ssrc []string, sdst string, arg *sjargs) error {

	if len(arg.sct) <= 0 {
		return errors.New("need link script file")
	}

	/**/
	cmd, _ := sjcc.NewLink(scpu, ssrc, sdst)
	cmd.AddArgs([]string{"-T", arg.sct})
	cmd.AddArgs(arg.ldargs)

	/**/
	exx, err := cmd.CreateExec()
	if err != nil {
		return err
	}

	/**/
	if arg.verb {
		fmt.Println(">> ", strings.Join(exx.Args, " "))
	}

	arult, err := exx.CombinedOutput()
	if len(arult) > 0 {
		fmt.Printf("%s\n", arult)
	}

	/**/
	return err
}

func main() {

	var sdst string

	/**/
	arg, err := myParseArgs(os.Args)
	if err != nil {
		fmt.Println("args:", err)
		os.Exit(1)
	}

	if arg.comp == true {

		/* source file, only one */
		if len(arg.src) != 1 {
			fmt.Println("args err, compile, must 1 source file")
			fmt.Println(arg.src)
			os.Exit(1)
		}

		/* output file */
		if len(arg.dst) == 0 {
			sdst = arg.src[0] + ".o"
		} else {
			sdst = arg.dst
		}

		/* cpu type */
		if len(arg.cpu) == 0 {
			fmt.Println("args err, need cpu type.")
			os.Exit(1)
		}

		/**/
		ext := strings.ToLower(filepath.Ext(arg.src[0]))
		if ext == ".c" {

			err := myCompile(arg.cpu, arg.src[0], sdst, arg)
			if err != nil {
				fmt.Println("comp err,", err)
				os.Exit(2)
			}

		} else if ext == ".s" {

			err := myAssemble(arg.cpu, arg.src[0], sdst, arg)
			if err != nil {
				fmt.Println("asm err,", err)
				os.Exit(2)
			}

		} else {
			fmt.Println("ext err,", ext)
			os.Exit(3)
		}

		/* compile, succ */
		os.Exit(0)

	} else {

		if len(arg.src) <= 0 {
			fmt.Println("args err, compile, no input file")
			os.Exit(1)
		}

		objs := make([]string, len(arg.src))

		for i, v := range arg.src {

			/**/
			ext := strings.ToLower(filepath.Ext(v))
			if ext == ".c" {

				objs[i] = v + ".o"
				err := myCompile(arg.cpu, v, objs[i], arg)
				if err != nil {
					fmt.Println("comp err,", err)
					os.Exit(2)
				}

			} else if ext == ".s" {

				objs[i] = v + ".o"
				err := myAssemble(arg.cpu, v, objs[i], arg)
				if err != nil {
					fmt.Println("asm err,", err)
					os.Exit(2)
				}

			} else {
				objs[i] = v
			}
		}

		if len(arg.dst) != 0 {
			sdst = arg.dst
		} else {
			sdst = "a.out"
		}

		/**/
		err = myLink(arg.cpu, objs, sdst, arg)
		if err != nil {
			fmt.Println("link:", err)
			os.Exit(3)
		}
	}

	/**/
	os.Exit(0)
	return
}
